#include <iostream>
#include <csignal>
#include <cerrno>
#include <cstring>

#include <sys/un.h>
#include <unistd.h>

#include "event.h"
#include "event2/bufferevent.h"
#include "event2/buffer.h"
#include "event2/listener.h"

#define PATH "/tmp/t-libev.sock"

using namespace std;

static void on_sigint(int sig, short events, void *arg) {
    struct event_base *base = (struct event_base *)arg;
    struct timeval delay = { 2, 0 };
    //event_base_loopbreak(base);
    cerr << "[ W ] Caught an interrupt signal: " << sig << ", exiting cleanly in two seconds." << endl;
    event_base_loopexit(base, &delay);
}

static void conn_readcb(struct bufferevent *bev, void *user_data) {
    cout << "[ I ] conn_readcb..." << endl;
}

static void conn_writecb(struct bufferevent *bev, void *user_data) {
    cout << "[ I ] conn_writecb..." << endl;
    struct evbuffer *output = bufferevent_get_output(bev);

    if (evbuffer_get_length(output) == 0) {
        cout << "[ I ] flushed answer." << endl;
        bufferevent_free(bev);
    }
}

static void conn_eventcb(struct bufferevent *bev, short events, void *user_data) {
    cout << "[ I ] conn_eventcb..." << endl;

    if (events & BEV_EVENT_EOF) {
        cout << "[ I ] Connection closed." << endl;
    } else if (events & BEV_EVENT_ERROR) {
        cout << "[ W ] Got an error on the connection: " << strerror(errno) << endl;
    }

    bufferevent_free(bev);
}

static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
            struct sockaddr *sa, int socklen, void *arg) {
    struct event_base *base = (struct event_base *)arg;
    struct bufferevent *bev;

    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        cerr << "[ E ] Error constructing bufferevent!" << endl;
        event_base_loopbreak(base);
    } else {
        bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
    }
}

int main() {
    int ret = -1;

    struct event_base *base = NULL;
    struct event *term = NULL;
    struct evconnlistener *lev = NULL;

    struct sockaddr_un addr = { 0 };

    cout << "[ I ] begin... " << endl;

    base = event_base_new();

    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    term = evsignal_new(base, SIGINT, on_sigint, base);
    if (!term) {
        cerr << "[ E ] failed create sigint event." << endl;
        ret = 3;
        goto err;
    }

    if (event_add(term, NULL)) {
        cerr << "[ E ] failed add sigint event." << endl;
        ret = 2;
        goto err;
    }

    if ((unlink(PATH) && errno != ENOENT)) {
        perror(PATH);
        ret = 1;
        goto err;
    }

    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, PATH, strlen(PATH) + 1);

    lev = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
            (struct sockaddr *)&addr, sizeof(addr));
    if (!lev) {
        perror("Cannot create listener");
        ret = 4;
        goto err;
    }

    event_base_dispatch(base);

    cout << "[ I ] shutdown." << endl;

    ret = 0;
err:
    if (term)
        event_free(term);
    if (lev)
        evconnlistener_free(lev);
    if (base)
        event_base_free(base);

    return ret;
}
